package com.haohan.cloud.scm.opc.core.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.haohan.cloud.scm.api.constant.enums.common.SettlementTypeEnum;
import com.haohan.cloud.scm.api.constant.enums.manage.PhotoTypeEnum;
import com.haohan.cloud.scm.api.constant.enums.opc.YesNoEnum;
import com.haohan.cloud.scm.api.constant.enums.purchase.PayTypeEnum;
import com.haohan.cloud.scm.api.manage.dto.PhotoGroupDTO;
import com.haohan.cloud.scm.api.manage.entity.PhotoGallery;
import com.haohan.cloud.scm.api.opc.dto.BillPayment;
import com.haohan.cloud.scm.api.opc.dto.BillPaymentInfoDTO;
import com.haohan.cloud.scm.api.opc.dto.SettlementRecordDTO;
import com.haohan.cloud.scm.api.opc.entity.BuyerPayment;
import com.haohan.cloud.scm.api.opc.entity.SettlementRecord;
import com.haohan.cloud.scm.api.opc.entity.SettlementRelation;
import com.haohan.cloud.scm.api.opc.entity.SupplierPayment;
import com.haohan.cloud.scm.api.opc.req.CompleteSettlementReq;
import com.haohan.cloud.scm.api.opc.req.ReadySettlementReq;
import com.haohan.cloud.scm.api.opc.req.settlement.SettlementPaymentReq;
import com.haohan.cloud.scm.api.opc.trans.SettlementTrans;
import com.haohan.cloud.scm.api.pay.entity.OrderPayRecord;
import com.haohan.cloud.scm.opc.core.BillPaymentCoreService;
import com.haohan.cloud.scm.opc.core.IBuyerPaymentCoreService;
import com.haohan.cloud.scm.opc.core.IScmOpcSettlementService;
import com.haohan.cloud.scm.opc.core.ISupplierPaymentCoreService;
import com.haohan.cloud.scm.opc.service.BuyerPaymentService;
import com.haohan.cloud.scm.opc.service.SettlementRecordService;
import com.haohan.cloud.scm.opc.service.SettlementRelationService;
import com.haohan.cloud.scm.opc.service.SupplierPaymentService;
import com.haohan.cloud.scm.opc.utils.ScmOpcUtils;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author dy
 * @date 2019/7/25
 */
@Service
@AllArgsConstructor
public class ScmOpcSettlementServiceImpl implements IScmOpcSettlementService {

    private final BillPaymentCoreService billPaymentCoreService;
    private final SettlementRelationService relationService;
    private final SettlementRecordService settlementRecordService;
    private final BuyerPaymentService buyerPaymentService;
    private final IBuyerPaymentCoreService buyerPaymentCoreService;
    private final SupplierPaymentService supplierPaymentService;
    private final ISupplierPaymentCoreService supplierPaymentCoreService;
    private final ScmOpcUtils scmOpcUtils;

    /**
     * 准备结算  多个账单
     * 先判断对应的账单 是否已审核
     * 通过时会创建对应结算记录(初始化,未结算)
     * 失败时不创建结算记录
     *
     * @param req 必需:pmId/ paymentList
     * @return SettlementRecord 失败时返回null
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<SettlementRecord> readySettlement(ReadySettlementReq req) {
        String pmId = req.getPmId();
        // 账单未结算且已审核
        List<BuyerPayment> receivableList = new ArrayList<>(10);
        List<SupplierPayment> payableList = new ArrayList<>(10);
        // 应收/应付金额
        BigDecimal receivableAmount = BigDecimal.ZERO;
        BigDecimal payableAmount = BigDecimal.ZERO;
        String merchantId = null;
        String merchantName = null;
        // 选择相同账单结算时为同一笔结算
        Set<String> settlementSnSet = new HashSet<>(8);
        Set<String> paymentSnSet = new HashSet<>(16);
        String paymentSn;
        for (SettlementPaymentReq payment : req.getPaymentList()) {
            paymentSn = payment.getPaymentSn();
            paymentSnSet.add(paymentSn);
            switch (payment.getSettlementType()) {
                case receivable:
                    // 应收
                    BuyerPayment query = new BuyerPayment();
                    query.setPmId(pmId);
                    query.setBuyerPaymentId(paymentSn);
                    BuyerPayment receivablePayment = buyerPaymentCoreService.checkPayment(query);
                    if (null == receivablePayment) {
                        return R.ok(null, "当前账单未确认,不可结算：" + paymentSn);
                    }
                    if (YesNoEnum.no != receivablePayment.getStatus()) {
                        return R.ok(null, "当前账单已结算：" + paymentSn);
                    }
                    if (StrUtil.isBlank(merchantId)) {
                        merchantId = receivablePayment.getMerchantId();
                        merchantName = receivablePayment.getMerchantName();
                    } else if (!StrUtil.equals(merchantId, receivablePayment.getMerchantId())) {
                        return R.ok(null, "当前账单结算商家不相同：" + paymentSn);
                    }
                    receivableList.add(receivablePayment);
                    receivableAmount = receivableAmount.add(receivablePayment.getBuyerPayment());
                    if (StrUtil.isNotBlank(receivablePayment.getSettlementSn())) {
                        settlementSnSet.add(receivablePayment.getSettlementSn());
                    }
                    break;
                case payable:
                    // 应付
                    SupplierPayment payableQuery = new SupplierPayment();
                    payableQuery.setPmId(pmId);
                    payableQuery.setSupplierPaymentId(paymentSn);
                    SupplierPayment payablePayment = supplierPaymentCoreService.checkPayment(payableQuery);
                    if (null == payablePayment) {
                        return R.ok(null, "当前账单未确认,不可结算：" + paymentSn);
                    }
                    if (YesNoEnum.no != payablePayment.getStatus()) {
                        return R.ok(null, "当前账单已结算：" + paymentSn);
                    }
                    if (StrUtil.isBlank(merchantId)) {
                        merchantId = payablePayment.getMerchantId();
                        merchantName = payablePayment.getMerchantName();
                    } else if (!StrUtil.equals(merchantId, payablePayment.getMerchantId())) {
                        return R.ok(null, "当前账单结算商家不相同：" + paymentSn);
                    }
                    payableList.add(payablePayment);
                    payableAmount = payableAmount.add(payablePayment.getSupplierPayment());
                    if (StrUtil.isNotBlank(payablePayment.getSettlementSn())) {
                        settlementSnSet.add(payablePayment.getSettlementSn());
                    }
                    break;
                default:
                    return R.ok(null, "当前账单类型有误：" + paymentSn);
            }
        }
        // 判断是否同一笔 再次发起结算/不为同一笔则创建失败
        SettlementRelation relation = null;
        // 创建失败标识
        boolean flag = false;
        if (settlementSnSet.size() > 1) {
            flag = true;
        } else if (settlementSnSet.size() == 1) {
            relation = checkSettlement(settlementSnSet, paymentSnSet);
            if (null == relation) {
                flag = true;
            }
        }
        if (flag) {
            return R.ok(null, "当前选择账单已存在不同结算单:" + CollUtil.join(settlementSnSet, ","));
        }
        // 创建结算单
        SettlementRecord result = new SettlementRecord();
        result.setPmId(pmId);
        result.setPayType(PayTypeEnum.cash);
        result.setStatus(YesNoEnum.no);
        result.setCompanyId(merchantId);
        result.setCompanyName(merchantName);
        // 结算金额
        BigDecimal settlementAmount = receivableAmount.subtract(payableAmount);
        result.setSettlementType((settlementAmount.compareTo(BigDecimal.ZERO) < 0) ? SettlementTypeEnum.payable : SettlementTypeEnum.receivable);
        result.setSettlementAmount(settlementAmount.abs());
        if (null == relation) {
            settlementRecordService.save(result);
        } else {
            result.setId(relation.getSettlementId());
            result.setSettlementSn(relation.getSettlementSn());
            settlementRecordService.updateById(result);
        }
        // 创建 结算单/账单 关联关系   若已关联结算单，不修改对应关联关系
        addRelations(receivableList, payableList, result);
        return R.ok(result);
    }

    /**
     * 检查结算单，  为同一笔时返回id
     *
     * @param settlementSnSet
     * @param paymentSnSet
     * @return SettlementRelation  id /sn
     */
    private SettlementRelation checkSettlement(Set<String> settlementSnSet, Set<String> paymentSnSet) {
        SettlementRelation result = null;
        for (String sn : settlementSnSet) {
            for (SettlementRelation relation : relationService.fetchListBySn(sn)) {
                if (null == result) {
                    result = relation;
                }
                if (!paymentSnSet.contains(relation.getPaymentSn())) {
                    return null;
                }
            }
        }
        return result;
    }

    /**
     * 创建 结算单/账单 关联关系   若已关联结算单，不修改对应关联关系
     *
     * @param receivableList
     * @param payableList
     * @param result
     */
    private void addRelations(List<BuyerPayment> receivableList, List<SupplierPayment> payableList, SettlementRecord result) {
        String settlementSn = result.getSettlementSn();
        SettlementRelation relation;
        BuyerPayment updateReceivable = new BuyerPayment();
        updateReceivable.setSettlementSn(settlementSn);
        for (BuyerPayment b : receivableList) {
            // 设置结算单号
            updateReceivable.setId(b.getId());
            buyerPaymentService.updateById(updateReceivable);
            // 关联关系新增
            if (StrUtil.isEmpty(b.getSettlementSn())) {
                relation = SettlementTrans.initRelation(result, new BillPayment(b));
                relationService.save(relation);
            }
        }
        SupplierPayment updatePayable = new SupplierPayment();
        updatePayable.setSettlementSn(settlementSn);
        for (SupplierPayment s : payableList) {
            // 设置结算单号
            updatePayable.setId(s.getId());
            supplierPaymentService.updateById(updatePayable);
            // 关联关系新增
            if (StrUtil.isEmpty(s.getSettlementSn())) {
                relation = SettlementTrans.initRelation(result, new BillPayment(s));
                relationService.save(relation);
            }
        }
    }

    /**
     * 在线支付后完成结算
     *
     * @param payRecord
     * @return
     */
    @Override
    public R<Boolean> payToSettlement(OrderPayRecord payRecord) {
        SettlementRecord settlementRecord = new SettlementRecord();
        settlementRecord.setSettlementSn(payRecord.getSettlementSn());
        settlementRecord.setSettlementAmount(payRecord.getOrderAmount());
        settlementRecord.setPayType(PayTypeEnum.online);
        settlementRecord.setPayDate(payRecord.getOrderTime());

        settlementRecord.setSettlementDesc("在线支付:" + payRecord.getOrderId());
        return completeSettlement(settlementRecord);
    }

    /**
     * 完成结算
     * 若提交金额和账单金额不一致 不结算
     * 修改对应账单状态
     *
     * @param settlementRecord 必需:settlementSn /settlementAmount / payType/
     *                         / payDate / settlementImg/companyOperator/operator
     *                         可选:settlementDesc
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> completeSettlement(SettlementRecord settlementRecord) {
        // 结算单有则修改状态
        SettlementRecord origin = settlementRecordService.fetchBySn(settlementRecord);
        if (null == origin || origin.getStatus() == YesNoEnum.yes) {
            return R.ok(false, "不可结算");
        }

        // 查询关联账单
        List<SettlementRelation> relationList = relationService.fetchListBySn(origin.getSettlementSn());
        if (CollUtil.isEmpty(relationList)) {
            return R.ok(false, "结算单有误，无对应账单");
        }
        // 账单查询  及修改状态
        List<BuyerPayment> receivableList = new ArrayList<>(relationList.size());
        List<SupplierPayment> payableList = new ArrayList<>(relationList.size());
        BigDecimal receivableAmount = BigDecimal.ZERO;
        BigDecimal payableAmount = BigDecimal.ZERO;

        for (SettlementRelation relation : relationList) {
            if (relation.getSettlementType() == null) {
                continue;
            }
            String paymentId = relation.getPaymentId();
            switch (relation.getSettlementType()) {
                case receivable:
                    // 应收
                    BuyerPayment buyerPayment = buyerPaymentService.getById(paymentId);
                    if (null == buyerPayment) {
                        return R.ok(false, "结算账单有误");
                    }
                    receivableList.add(buyerPayment);
                    receivableAmount = receivableAmount.add(buyerPayment.getBuyerPayment());
                    break;
                case payable:
                    // 应付
                    SupplierPayment supplierPayment = supplierPaymentService.getById(paymentId);
                    if (null == supplierPayment) {
                        return R.ok(false, "结算账单有误");
                    }
                    payableList.add(supplierPayment);
                    payableAmount = payableAmount.add(supplierPayment.getSupplierPayment());
                    break;
                default:
            }
        }
        // 结算金额
        BigDecimal settlementAmount = receivableAmount.subtract(payableAmount).abs();
        if (settlementAmount.compareTo(settlementRecord.getSettlementAmount()) != 0) {
            return R.ok(false, "结算金额有误，需和账单金额相等");
        }
        // 修改结算单
        SettlementRecord update = SettlementTrans.completeSettlementTrans(origin.getId(), settlementRecord);
        if (!settlementRecordService.updateById(update)) {
            return R.ok(false, "结算单修改失败");
        }
        // 修改账单状态
        BuyerPayment updateReceivable = new BuyerPayment();
        updateReceivable.setStatus(YesNoEnum.yes);
        for (BuyerPayment b : receivableList) {
            // 设置结算单号
            updateReceivable.setId(b.getId());
            buyerPaymentService.updateById(updateReceivable);
        }
        SupplierPayment updatePayable = new SupplierPayment();
        updatePayable.setStatus(YesNoEnum.yes);
        for (SupplierPayment s : payableList) {
            // 设置结算单号
            updatePayable.setId(s.getId());
            supplierPaymentService.updateById(updatePayable);
        }
        return R.ok(true);
    }

    /**
     * 完成结算  先处理图片
     *
     * @param req
     * @return
     */
    @Override
    public R<Boolean> completeSettlementWithPhoto(CompleteSettlementReq req) {
        String groupNum = req.getGroupNum();
        // 修改图片组图片
        List<PhotoGallery> photoList = new ArrayList<>();
        for (String id : req.getPhotoIdList()) {
            if (StrUtil.isNotBlank(id)) {
                PhotoGallery photoGallery = new PhotoGallery();
                photoGallery.setId(id);
                photoList.add(photoGallery);
            }
        }
        PhotoGroupDTO photoGroupDTO = new PhotoGroupDTO();
        if (StrUtil.isNotBlank(groupNum)) {
            photoGroupDTO.setGroupNum(groupNum);
        } else {
            photoGroupDTO.setGroupName("商家结算");
            photoGroupDTO.setMerchantId(req.getPmId());
            photoGroupDTO.setCategoryTag(PhotoTypeEnum.billPhotos.getDesc());
            photoGroupDTO.setPhotoList(photoList);
        }
        groupNum = scmOpcUtils.savePhotoGroup(photoGroupDTO);
        req.setGroupNum(groupNum);
        return completeSettlement(req.transTo());
    }

    @Override
    public SettlementRecordDTO queryInfo(SettlementRecord settlementRecord) {
        SettlementRecord record = settlementRecordService.fetchBySn(settlementRecord);
        if (null == record || record.getSettlementType() == null) {
            return null;
        }
        SettlementRecordDTO settlementRecordDTO = new SettlementRecordDTO();
        BeanUtil.copyProperties(record, settlementRecordDTO);
        // 图片 列表查询
        settlementRecordDTO.setPhotoList(scmOpcUtils.fetchPhotoGroup(record.getGroupNum()));
        List<BillPaymentInfoDTO> billList = new ArrayList<>(10);
        // 账单详情
        List<SettlementRelation> relationList = relationService.fetchListBySn(record.getSettlementSn());
        for (SettlementRelation relation : relationList) {
            BillPayment payment = new BillPayment();
            payment.setBillSn(relation.getPaymentSn());
            payment.setSettlementType(relation.getSettlementType());
            BillPaymentInfoDTO billPaymentInfo = billPaymentCoreService.fetchBillInfo(payment);
            billList.add(billPaymentInfo);
        }
        settlementRecordDTO.setBillList(billList);
        return settlementRecordDTO;
    }

}
